// OpAnnotationDwg.ts

import {
  Plane,
  Raycaster,
  Vector3,
  Vector2,
  Mesh,
  Scene,
  Object3D,
  Font,
  LineBasicMaterial,
  Material,
  CircleBufferGeometry,
} from "../../WebViewer/THREE/Three";
// @ts-ignore
import { AutoTransform } from "../../WebViewer/Core/AutoTransform.js";

let currentLeft: number;
let currentTop: number;
let currentPageX: number;
let currentPageY: number;
let isMobileMove: boolean;
let isDownMove: boolean;

let _viewZ = new Vector3();
let _viewPlane = new Plane();
let _rayCaster = new Raycaster();
// @ts-ignore
_rayCaster.params.Line.threshold = 10;

class OpAnnotationDwg {
  app: any;

  private _onPointerDown: (event: any) => void;
  private _onPointerMove: (event: any) => void;
  private _onPointerUp: (event: any) => void;
  private _onMouseZoom: (event: any) => void;
  private _onKeyDown: (event: any) => void;

  tempAnnotation: AnnotationMesh | null;
  selectedAnnotation: AnnotationMesh | null;
  editDragIndex: number;

  annotationMeshArr: Array<AnnotationMesh>;

  lineSize: number;
  textSize: number;
  color: string;

  mouseSimulator: HTMLElement | null;

  constructor(app: any) {
    this.app = app;

    this._onPointerDown = onPointerDown.bind(this);
    this._onPointerMove = onPointerMove.bind(this);
    this._onPointerUp = onPointerUp.bind(this);
    this._onMouseZoom = onMouseZoom.bind(this);
    this._onKeyDown = onKeyDown.bind(this);

    this.tempAnnotation = null;
    this.selectedAnnotation = null;
    this.editDragIndex = -1;

    this.annotationMeshArr = [];

    this.lineSize = 2;
    this.textSize = 500;
    this.color = "#ff0000";

    this.mouseSimulator = null;
  }

  initEvent() {
    if (isMobile()) {
      this.mouseSimulator = document.createElement("div");
      this.mouseSimulator.setAttribute("class", "ccbim__mouseImg");
      const html = `<div class="ccbim__mouseImg__imshar"></div><img src='' alt='模拟鼠标'/>`;
      this.mouseSimulator.innerHTML = html;
      this.app.parentElement.appendChild(this.mouseSimulator);
      this.mouseSimulator.lastChild.src = "../../assets/images/mouse1.png";

      this.mouseSimulator.addEventListener("touchstart", this._onPointerDown);
      this.mouseSimulator.addEventListener("touchmove", this._onPointerMove);
      this.mouseSimulator.addEventListener("touchend", this._onPointerUp);
    } else {
      this.app.bimViewer.dom.addEventListener(
        "pointerdown",
        this._onPointerDown
      );
      this.app.bimViewer.dom.addEventListener(
        "pointermove",
        this._onPointerMove
      );
      this.app.bimViewer.dom.addEventListener("pointerup", this._onPointerUp);
      this.app.bimViewer.dom.addEventListener("wheel", this._onMouseZoom);
      document.addEventListener("keydown", this._onKeyDown);
    }
  }

  disposeEvent() {
    if (isMobile()) {
      if (this.mouseSimulator) {
        this.mouseSimulator.removeEventListener(
          "touchstart",
          this._onPointerDown
        );
        this.mouseSimulator.removeEventListener(
          "touchmove",
          this._onPointerMove
        );
        this.mouseSimulator.removeEventListener("touchend", this._onPointerUp);

        this.app.parentElement.removeChild(this.mouseSimulator);
        this.mouseSimulator = null;
      }
    } else {
      this.app.bimViewer.dom.removeEventListener(
        "pointerdown",
        this._onPointerDown
      );
      this.app.bimViewer.dom.removeEventListener(
        "pointermove",
        this._onPointerMove
      );
      this.app.bimViewer.dom.removeEventListener(
        "pointerup",
        this._onPointerUp
      );
    }

    this.stopAnnotation();
    this.setSelectAnnotation(null);
  }

  private pointerDown(e: any) {
    if (e.button === 0) e.preventDefault();

    isMobileMove = false;

    if (isMobile()) {
      e.preventDefault();

      if (this.mouseSimulator) {
        currentLeft = this.mouseSimulator.offsetLeft;
        currentTop = this.mouseSimulator.offsetTop;

        this.mouseSimulator.lastChild.src = "../../assets/images/mouse2.png";
      }

      currentPageX = e.touches[0].pageX;
      currentPageY = e.touches[0].pageY;
    } else {
      this.handleClick(e);
    }
  }

  private pointerMove(e: any): void {
    this.app.getViewer().cameraChanged();

    let event: any = e;

    if (event.buttons & 2 || event.buttons & 4) {
      isDownMove = true;
    }

    if (isMobile()) {
      e.preventDefault();

      let eMovePageX = e.touches[0].pageX;
      let eMovePageY = e.touches[0].pageY;

      if (
        Math.abs(eMovePageX - currentPageX) > 20 ||
        Math.abs(eMovePageY - currentPageY) > 20
      ) {
        isMobileMove = true;
      }

      let left = eMovePageX - currentPageX + currentLeft;
      let top = eMovePageY - currentPageY + currentTop;

      left < 0 && (left = 0);
      top < 0 && (top = 0);

      if (this.mouseSimulator && this.mouseSimulator.parentElement) {
        let maxLeft =
          this.mouseSimulator.parentElement.clientWidth -
          this.mouseSimulator.clientWidth;
        let maxTop =
          this.mouseSimulator.parentElement.clientHeight -
          this.mouseSimulator.clientHeight;

        left > maxLeft && (left = maxLeft);
        top > maxTop && (top = maxTop);

        this.mouseSimulator.style.left = left + "px";
        this.mouseSimulator.style.top = top + "px";

        event = {
          offsetX: left,
          offsetY: top,
        };
      }
    }

    let point = this.getMouseWorldPos(event);

    if (this.tempAnnotation) {
      const index = this.tempAnnotation.ptsArr.length - 1;
      const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
      this.tempAnnotation.editPoint({ point, index, pixelScale2d });

      return;
    }

    if (this.editDragIndex > -1) {
      const index = this.editDragIndex;
      const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
      this.selectedAnnotation?.editPoint({ point, index, pixelScale2d });

      return;
    }

    this.trySelectMeasure(event);
    this.trySelectDrag(event);
  }

  private pointerUp(e: any): void {
    if (isMobile()) {
      e.preventDefault();

      this.mouseSimulator.lastChild.src = "../../assets/images/mouse1.png";

      this.handleClick(e);
    }
  }

  private handleClick(e: PointerEvent) {
    let event: any = e;

    if (isMobile()) {
      if (isMobileMove) return;

      if (this.mouseSimulator) {
        const left = this.mouseSimulator.offsetLeft;
        const top = this.mouseSimulator.offsetTop;
        event = {
          offsetX: left,
          offsetY: top,
        };
      }
    }

    if (isDownMove) {
      isDownMove = false;
      return;
    }

    let point = this.getMouseWorldPos(event);

    if (this.tempAnnotation) {
      this.tempAnnotationPointerUp({ event, point });
      return;
    }

    const selectedAnnotation = this.trySelectMeasure(event);
    if (selectedAnnotation) {
      this.setSelectAnnotation(selectedAnnotation);
      return;
    }

    const selectDrag = this.trySelectDrag(event);
    if (selectDrag) {
      if (this.editDragIndex === -1) {
        this.editDragIndex = selectDrag.index;
      } else {
        this.editDragIndex = -1;
      }
      return;
    }

    if (event.button === 1 || event.button === 2) return;

    const measure = this.createNewAnnotation(point);
    if (measure) {
      this.tempAnnotation = measure;
      this.annotationMeshArr.push(measure);
    }

    this.setSelectAnnotation(null);
  }

  private mouseZoom(e: Event) {
    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    this.annotationMeshArr.forEach((item) => {
      item.updateLineTextShape(pixelScale2d);
    });
    this.app.getViewer().cameraChanged();
  }

  private keyDown(e: KeyboardEvent) {
    if (e.keyCode === 27) {
      if (this.tempAnnotation) {
        this.tempAnnotation.dispose();
        this.tempAnnotation = null;
        this.app.getViewer().cameraChanged();
      }
    }
  }

  tempAnnotationPointerUp(param: { event: PointerEvent; point: Vector3 }) {}

  createNewAnnotation(point: Vector3): any {}

  stopAnnotation() {
    this.setSelectAnnotation(this.tempAnnotation);

    this.tempAnnotation = null;
  }

  trySelectMeasure(e: PointerEvent) {
    this.getMouseRaycaster(e, _rayCaster);

    let measure = null;
    this.annotationMeshArr.forEach((item) => {
      if (item !== this.selectedAnnotation) {
        const interse = item.intersectObjects(_rayCaster);
        if (interse) {
          measure = item;
          item.changeMeshMaterial(item.hoverMaterial);
        } else {
          item.changeMeshMaterial(item.material);
        }
      } else {
        item.changeMeshMaterial(item.material);
      }
    });

    return measure;
  }

  trySelectDrag(e: PointerEvent): any {
    this.getMouseRaycaster(e, _rayCaster);

    let drag = null;
    this.annotationMeshArr.forEach((item) => {
      if (item === this.selectedAnnotation) {
        const interse = item.intersectDrag(_rayCaster);
        drag = interse;
      }
    });

    return drag;
  }

  setSelectAnnotation(mesh: AnnotationMesh | null) {
    this.annotationMeshArr.forEach((item) => {
      item.setDragHandleVisible(false);
    });

    if (mesh) {
      this.selectedAnnotation = mesh;
      this.selectedAnnotation.setDragHandleVisible(true);
    } else {
      this.selectedAnnotation = null;
    }
    this.app.getViewer().cameraChanged();
  }

  deleteSelectAnnotation() {
    if (!this.selectedAnnotation) return;

    for (let i = 0, il = this.annotationMeshArr.length; i < il; ++i) {
      if (this.annotationMeshArr[i] === this.selectedAnnotation) {
        this.annotationMeshArr.splice(i, 1);
        break;
      }
    }

    this.selectedAnnotation.dispose();
    this.selectedAnnotation = null;
  }

  setAnnotationMeshArrVisible(value: boolean) {
    this.annotationMeshArr.forEach((item) => {
      item.setMeshVisible(value);
    });
  }

  deleteAllAnnotation() {
    this.stopAnnotation();

    this.annotationMeshArr.forEach((item) => {
      item.dispose();
    });
    this.annotationMeshArr = [];
  }

  setNextLineSize(val: number) {
    this.lineSize = val;
  }

  setNextTextSize(val: number) {
    this.textSize = val;
  }

  setNextColor(color: string) {
    this.color = color;
  }

  getAnnotationData() {
    let data: any = [];
    this.annotationMeshArr.forEach((item) => {
      const d = item.getData();
      data.push(d);
    });

    return data;
  }

  setAnnotationData(data: Array<any>) {
    data.forEach((item) => {
      const measure = this.createAnnotationFromData(item);
      this.annotationMeshArr.push(measure);
    });
  }

  createAnnotationFromData(data: any): any {}

  dispose() {
    this.disposeEvent();
    this.deleteAllAnnotation();
  }

  getMouseWorldPos(e: PointerEvent) {
    const camera = this.app.bimViewer.getMainCamera();

    _viewZ.subVectors(camera.position, camera.target).normalize();
    _viewPlane.setFromNormalAndCoplanarPoint(_viewZ, camera.target);

    this.getMouseRaycaster(e, _rayCaster);

    let target = new Vector3();
    _rayCaster.ray.intersectPlane(_viewPlane, target);

    return target;
  }

  getMouseRaycaster(e: PointerEvent, rayster: Raycaster) {
    const _windowPos = this.getMouseWindowPos(e);

    const camera = this.app.bimViewer.getMainCamera();

    rayster.far = Infinity;
    rayster.near = camera.near;
    rayster.setFromCamera(_windowPos, camera);
  }

  getMouseWindowPos(e: PointerEvent) {
    const renderDomRect = this.app.bimViewer
      .getRenderDom()
      .getBoundingClientRect();

    let windowCoordinate = new Vector2();
    windowCoordinate.set(
      (e.offsetX / renderDomRect.width) * 2 - 1,
      (e.offsetY / renderDomRect.height) * -2 + 1
    );

    return windowCoordinate;
  }
}

function onPointerDown(this: any, e: PointerEvent) {
  this.pointerDown(e);
}

function onPointerMove(this: any, e: PointerEvent) {
  this.pointerMove(e);
}

function onPointerUp(this: any, e: PointerEvent) {
  this.pointerUp(e);
}

function onMouseZoom(this: any, e: Event) {
  this.mouseZoom(e);
}

function onKeyDown(this: any, e: Event) {
  this.keyDown(e);
}

class AnnotationMesh {
  ptsArr: Array<Vector3>;
  lineShape!: Mesh;
  dragHandleArr: Array<DragHadle>;

  material: LineBasicMaterial;
  hoverMaterial: LineBasicMaterial;

  scene: Scene;
  font: Font;

  lineSize: number;
  textSize: number;
  color: string;

  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    this.material = new LineBasicMaterial({ color: param.color });
    this.hoverMaterial = new LineBasicMaterial({ color: 0xffff00 });

    this.scene = param.scene;
    this.font = param.font;

    this.ptsArr = [param.start.clone(), param.end.clone()];
    this.dragHandleArr = [];

    this.lineSize = param.lineSize;
    this.textSize = param.textSize;
    this.color = param.color;
  }

  addPoint(point: Vector3) {
    this.ptsArr.push(point);

    const drag = this.createDragHandle(point);
    this.dragHandleArr.push(drag);
    drag.install(this.scene);
  }

  editPoint(param: { point: Vector3; index: number; pixelScale2d: number }) {
    this.ptsArr[param.index].copy(param.point);

    this.updateLineTextShape(param.pixelScale2d);
    this.updateDragShape(param.index - 1);
  }

  updateLineTextShape(pixelScale2d: number) {}

  updateDragShape(current: number) {
    this.dragHandleArr[current + 1].setPosition(this.ptsArr[current + 1]);
  }

  intersectObjects(_rayCaster: Raycaster) {
    let children: any = [];
    children.push(this.lineShape);

    const intersects = _rayCaster.intersectObjects(children, true);
    if (intersects.length > 0) {
      return true;
    }

    return false;
  }

  intersectDrag(_rayCaster: Raycaster) {
    for (let i = 0; i < this.dragHandleArr.length; i++) {
      const obj = this.dragHandleArr[i].autoTransform.children[0];
      const intersect = _rayCaster.intersectObject(obj, false);
      if (intersect.length > 0) {
        obj.material = this.hoverMaterial;

        return {
          obj: this.dragHandleArr[i],
          index: i,
        };
      } else {
        obj.material = this.material;
      }
    }

    return false;
  }

  changeMeshMaterial(material: Material) {
    if (this.lineShape.material === material) {
      return;
    }

    this.lineShape.material = material;
  }

  updateText() {}

  createDragHandle(point: Vector3) {
    const dragHandle = new DragHadle();
    dragHandle.setAutoScale(true);
    dragHandle.setAlignToScreen(true);

    const circle = new CircleBufferGeometry(6);
    dragHandle.add(new Mesh(circle, this.material));

    dragHandle.setPosition(point);
    dragHandle.setVisible(false);

    return dragHandle;
  }

  setDragHandleVisible(value: boolean) {
    this.dragHandleArr.forEach((item) => {
      item.setVisible(value);
    });
  }

  setMeshVisible(value: boolean) {
    if (value === false) {
      this.dragHandleArr.forEach((item) => {
        item.setVisible(value);
      });
    }

    this.lineShape.visible = value;
  }

  getData() {
    let pointArr: any = [];
    this.ptsArr.forEach((item) => {
      const arr = item.toArray();
      pointArr.push(arr);
    });

    let obj = {
      ptsArr: pointArr,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    };

    return obj;
  }

  setData() {}

  dispose() {
    this.dragHandleArr.forEach((item) => {
      item.unInstall(this.scene);
    });
    this.dragHandleArr = [];

    this.scene.remove(this.lineShape);
  }
}

class Handle {
  autoTransform: AutoTransform;

  constructor() {
    this.autoTransform = new AutoTransform();
  }

  install(scene: Scene) {
    scene.add(this.autoTransform);
  }

  unInstall(scene: Scene) {
    this.autoTransform.removeAll();
    scene.remove(this.autoTransform);
  }

  setAutoScale(value: boolean) {
    this.autoTransform.setAutoScale(value);
  }

  setAlignToScreen(value: boolean) {
    this.autoTransform.setAlignToScreen(value);
  }

  setPosition(value: Vector3) {
    this.autoTransform.setPosition(value);
  }

  getPosition() {
    return this.autoTransform.getPosition();
  }

  setVisible(value: boolean) {
    this.autoTransform.visible = value;
  }

  add(mesh: Object3D) {
    this.autoTransform.add(mesh);
  }

  removeAll() {
    this.autoTransform.removeAll();
  }
}

class DragHadle extends Handle {
  constructor() {
    super();
  }
}

function isMobile() {
  var userAgent = navigator.userAgent;
  return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
    userAgent
  );
}

export { OpAnnotationDwg, AnnotationMesh, DragHadle };
